#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
豆包音乐下载工具 v1.4 - 修复版
用于下载豆包音乐分享链接中的音乐
support普通模式和Selenium浏览器自动化模式，新增GUI界面支持
"""

import os
import re
import sys
import json
import time
import threading
import logging
import requests
import platform
from urllib.parse import urlparse, parse_qs
import argparse
from datetime import datetime
from logging.handlers import RotatingFileHandler

# 配置日志系统
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(levelname)s - %(message)s',
    handlers=[
        logging.StreamHandler(),
        RotatingFileHandler(
            'doubao_downloader.log',
            maxBytes=5*1024*1024,  # 5MB
            backupCount=3
        )
    ]
)
logger = logging.getLogger('doubao_downloader')

class Logger:
    """自定义日志类，提供更友好的日志输出和进度显示"""
    
    LEVELS = {
        'DEBUG': 10,
        'INFO': 20,
        'WARNING': 30,
        'ERROR': 40,
        'CRITICAL': 50
    }
    
    COLORS = {
        'DEBUG': '\033[94m',  # 蓝色
        'INFO': '\033[92m',   # 绿色
        'WARNING': '\033[93m', # 黄色
        'ERROR': '\033[91m',   # 红色
        'CRITICAL': '\033[95m', # 紫色
        'RESET': '\033[0m'
    }
    
    def __init__(self, level='INFO'):
        self.level = self.LEVELS.get(level, 20)
        self.last_progress_update = 0
    
    def _log(self, level_name, message):
        """输出日志信息"""
        if self.LEVELS.get(level_name, 20) >= self.level:
            timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
            # 控制台彩色输出
            if sys.stdout.isatty() and os.name != 'nt':
                colored_message = f"{self.COLORS[level_name]}{timestamp} [{level_name}] {message}{self.COLORS['RESET']}"
                print(colored_message)
            else:
                # Windows不支持ANSI颜色，使用普通输出
                print(f"{timestamp} [{level_name}] {message}")
            
            # 写入日志文件（使用Python标准日志库）
            logger_func = getattr(logger, level_name.lower())
            logger_func(message)
    
    def debug(self, message):
        self._log('DEBUG', message)
    
    def info(self, message):
        self._log('INFO', message)
    
    def warning(self, message):
        self._log('WARNING', message)
    
    def error(self, message):
        self._log('ERROR', message)
    
    def critical(self, message):
        self._log('CRITICAL', message)
    
    def progress(self, current, total, file_name=""):
        """显示下载进度条"""
        if total > 0:
            # 限制进度条更新频率，避免刷新过快
            current_time = time.time()
            if current_time - self.last_progress_update < 0.1 and current < total:
                return
            
            self.last_progress_update = current_time
            
            # 计算进度百分比
            percentage = (current / total) * 100
            
            # 格式化进度显示
            bar_length = 30
            filled_length = int(bar_length * current // total)
            bar = '█' * filled_length + '-' * (bar_length - filled_length)
            
            # 格式化大小显示
            current_size = self.format_size(current)
            total_size = self.format_size(total)
            
            # 构建进度信息
            progress_info = f"[{bar}] {percentage:.1f}% | {current_size}/{total_size}"
            if file_name:
                progress_info = f"{file_name[:20]}... | {progress_info}"
            
            # 在同一行更新进度
            sys.stdout.write(f"\r{progress_info}")
            sys.stdout.flush()
    
    def format_size(self, size_bytes):
        """格式化文件大小显示"""
        for unit in ['B', 'KB', 'MB', 'GB']:
            if size_bytes < 1024 or unit == 'GB':
                return f"{size_bytes:.2f} {unit}"
            size_bytes /= 1024

# 创建全局日志实例
global_logger = Logger()

# 尝试导入Selenium相关模块（仅导入非GUI依赖）
try:
    from selenium import webdriver
    from selenium.webdriver.chrome.service import Service
    from selenium.webdriver.chrome.options import Options
    from selenium.webdriver.common.by import By
    from selenium.webdriver.support.ui import WebDriverWait
    from selenium.webdriver.support import expected_conditions as EC
    from selenium.common.exceptions import TimeoutException, WebDriverException
    selenium_available = True
    print("✅ Selenium模块已成功导入")
except ImportError:
    selenium_available = False
    print("⚠️ Selenium模块未安装，浏览器自动化功能将不可用")
    print("   如需使用浏览器自动化功能，请运行: pip install selenium")
    print("   并下载与您Chrome浏览器版本匹配的ChromeDriver")

# 设置请求头，模拟浏览器访问
HEADERS = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36',
    'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
    'Accept-Language': 'zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2',
    'Connection': 'keep-alive',
}

def sanitize_filename(filename):
    """清理文件名中的非法字符"""
    # 替换Windows文件名中的非法字符
    invalid_chars = r'/:*?"<>|'
    for char in invalid_chars:
        filename = filename.replace(char, '_')
    # 替换控制字符
    filename = ''.join(char for char in filename if ord(char) > 31)
    # 限制文件名长度
    max_length = 200
    if len(filename) > max_length:
        name, ext = os.path.splitext(filename)
        filename = name[:max_length - len(ext)] + ext
    return filename

def parse_share_url(share_url):
    """解析分享链接，提取视频ID"""
    try:
        # 解析URL
        parsed_url = urlparse(share_url)
        
        # 从查询参数中提取vid
        query_params = parse_qs(parsed_url.query)
        if 'vid' in query_params:
            return query_params['vid'][0]
        
        # 从路径中提取vid
        path = parsed_url.path
        vid_match = re.search(r'/video/(\w+)', path)
        if vid_match:
            return vid_match.group(1)
        
        # 尝试直接匹配URL中的vid格式
        vid_match = re.search(r'vid=(\w+)', share_url)
        if vid_match:
            return vid_match.group(1)
        
        global_logger.warning(f"无法从链接中提取视频ID: {share_url}")
        return None
    except Exception as e:
        global_logger.error(f"解析URL时出错: {str(e)}")
        return None

def get_music_info(vid):
    """使用API获取音乐信息"""
    try:
        # 构建API请求URL
        api_url = f"https://api.doubao.com/video/info?vid={vid}"
        
        # 发送请求
        response = requests.get(api_url, headers=HEADERS, timeout=10)
        response.raise_for_status()
        
        # 解析响应
        data = response.json()
        
        # 检查响应是否成功
        if data.get('code') == 0 and data.get('data'):
            music_info = {
                'title': data['data'].get('title', '未知歌曲'),
                'artist': data['data'].get('author', '未知艺术家'),
                'duration': data['data'].get('duration', 0),
                'audio_url': data['data'].get('play_url', '')
            }
            return music_info
        else:
            global_logger.warning(f"API返回错误: {data.get('error_msg', '未知错误')}")
            return None
    except Exception as e:
        global_logger.error(f"获取音乐信息时出错: {str(e)}")
        return None

def get_music_info_with_selenium(vid):
    """使用Selenium获取音乐信息"""
    if not selenium_available:
        global_logger.error("Selenium模块不可用")
        return None
    
    try:
        # 设置Chrome选项
        options = setup_chrome_driver(headless=True)
        if not options:
            return None
        
        # 创建WebDriver
        driver = webdriver.Chrome(options=options)
        driver.set_page_load_timeout(30)
        
        # 访问音乐页面
        music_url = f"https://www.doubao.com/video/{vid}"
        driver.get(music_url)
        
        # 等待页面加载完成
        time.sleep(5)
        
        # 尝试提取音乐信息
        title = "未知歌曲"
        artist = "未知艺术家"
        
        # 尝试多种方式获取标题
        try:
            title_elements = driver.find_elements(By.CSS_SELECTOR, 'h1, .title, .video-title')
            if title_elements:
                title = title_elements[0].text.strip()
        except:
            pass
        
        # 尝试多种方式获取艺术家
        try:
            artist_elements = driver.find_elements(By.CSS_SELECTOR, '.author, .creator, .user-name')
            if artist_elements:
                artist = artist_elements[0].text.strip()
        except:
            pass
        
        # 尝试提取音频URL
        audio_url = ""
        try:
            # 查找页面中的音频元素
            audio_elements = driver.find_elements(By.TAG_NAME, 'audio')
            if audio_elements:
                audio_url = audio_elements[0].get_attribute('src')
            
            # 如果没有找到音频元素，尝试查找视频元素的源
            if not audio_url:
                video_elements = driver.find_elements(By.TAG_NAME, 'video')
                if video_elements:
                    audio_url = video_elements[0].get_attribute('src')
            
            # 如果仍然没有找到，尝试从JavaScript变量中提取
            if not audio_url:
                audio_url = driver.execute_script(
                    "return document.querySelector('meta[property=\"og:audio\"]')?.getAttribute('content') || ''"
                )
        except Exception as e:
            global_logger.error(f"提取音频URL时出错: {str(e)}")
        
        driver.quit()
        
        if not audio_url:
            global_logger.error("无法找到音频URL")
            return None
        
        music_info = {
            'title': title,
            'artist': artist,
            'duration': 0,  # Selenium模式下可能无法准确获取时长
            'audio_url': audio_url
        }
        
        global_logger.info(f"Selenium模式下成功获取音乐信息: {music_info['artist']} - {music_info['title']}")
        return music_info
    except Exception as e:
        global_logger.error(f"Selenium获取音乐信息时出错: {str(e)}")
        import traceback
        global_logger.debug(traceback.format_exc())
        return None

def download_music_file(audio_url, save_path, thread=None):
    """下载音乐文件"""
    try:
        # 发送请求
        response = requests.get(audio_url, headers=HEADERS, stream=True, timeout=30)
        response.raise_for_status()
        
        # 获取文件大小
        total_size = int(response.headers.get('content-length', 0))
        
        # 创建保存目录
        os.makedirs(os.path.dirname(save_path), exist_ok=True)
        
        # 开始下载
        downloaded_size = 0
        with open(save_path, 'wb') as f:
            for chunk in response.iter_content(chunk_size=8192):
                if chunk:
                    f.write(chunk)
                    downloaded_size += len(chunk)
                    
                    # 更新进度
                    if thread:
                        progress = int((downloaded_size / total_size) * 100) if total_size > 0 else 0
                        thread.update_progress.emit(progress)
                        thread.update_download_speed.emit(thread._calculate_download_speed(downloaded_size))
                        thread.update_estimated_time.emit(thread._calculate_estimated_time(downloaded_size))
                    else:
                        global_logger.progress(downloaded_size, total_size, os.path.basename(save_path))
                    
                    # 检查是否需要停止下载
                    if thread and not thread.is_running:
                        global_logger.warning("下载已取消")
                        # 尝试删除不完整的文件
                        try:
                            if os.path.exists(save_path):
                                os.remove(save_path)
                        except:
                            pass
                        return False
        
        # 确保进度显示为100%
        if thread:
            thread.update_progress.emit(100)
        else:
            global_logger.progress(downloaded_size, total_size, os.path.basename(save_path))
            print()  # 换行
        
        # 验证文件大小
        if total_size > 0 and downloaded_size != total_size:
            global_logger.warning(f"文件大小不匹配，预期: {total_size}，实际: {downloaded_size}")
            return False
        
        global_logger.info(f"音乐文件下载成功: {save_path}")
        return True
    except Exception as e:
        global_logger.error(f"下载音乐文件时出错: {str(e)}")
        import traceback
        global_logger.debug(traceback.format_exc())
        
        # 尝试删除不完整的文件
        try:
            if os.path.exists(save_path):
                os.remove(save_path)
        except:
            pass
        
        return False

def download_music_with_selenium(share_url, save_path):
    """使用Selenium下载音乐"""
    if not selenium_available:
        global_logger.error("Selenium模块不可用")
        return False
    
    try:
        # 设置Chrome选项
        options = setup_chrome_driver(headless=True)
        if not options:
            return False
        
        # 创建WebDriver
        driver = webdriver.Chrome(options=options)
        driver.set_page_load_timeout(30)
        
        # 访问音乐页面
        driver.get(share_url)
        
        # 等待页面加载完成
        time.sleep(5)
        
        # 尝试查找并下载音频
        try:
            # 查找音频元素
            audio_elements = driver.find_elements(By.TAG_NAME, 'audio')
            if audio_elements:
                audio_url = audio_elements[0].get_attribute('src')
                if audio_url:
                    # 使用requests下载
                    response = requests.get(audio_url, headers=HEADERS, stream=True, timeout=30)
                    response.raise_for_status()
                    
                    # 创建保存目录
                    os.makedirs(os.path.dirname(save_path), exist_ok=True)
                    
                    # 写入文件
                    with open(save_path, 'wb') as f:
                        for chunk in response.iter_content(chunk_size=8192):
                            if chunk:
                                f.write(chunk)
                    
                    global_logger.info(f"Selenium模式下载成功: {save_path}")
                    driver.quit()
                    return True
                else:
                    global_logger.error("音频元素存在但没有src属性")
            else:
                global_logger.error("页面中没有找到音频元素")
        except Exception as e:
            global_logger.error(f"Selenium下载过程中出错: {str(e)}")
        
        driver.quit()
        return False
    except Exception as e:
        global_logger.error(f"Selenium下载错误: {str(e)}")
        import traceback
        global_logger.debug(traceback.format_exc())
        return False

def generate_tampermonkey_script():
    """生成Tampermonkey脚本"""
    script_content = '''// ==UserScript==
// @name         豆包音乐提取器
// @namespace    http://tampermonkey.net/
// @version      0.1
// @description  从豆包音乐页面提取音频链接并提供下载按钮
// @author       You
// @match        https://www.doubao.com/video/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=doubao.com
// @grant        none
// ==/UserScript==

(function() {
    'use strict';

    // 创建通知函数
    function showNotification(message, type = 'info') {
        // 创建通知元素
        const notification = document.createElement('div');
        notification.className = 'tampermonkey-notification';
        notification.textContent = message;
        
        // 设置样式
        Object.assign(notification.style, {
            position: 'fixed',
            top: '20px',
            right: '20px',
            padding: '15px 20px',
            borderRadius: '8px',
            color: 'white',
            fontWeight: 'bold',
            zIndex: '999999',
            fontSize: '14px',
            boxShadow: '0 4px 12px rgba(0, 0, 0, 0.15)',
            opacity: '0',
            transition: 'opacity 0.3s ease'
        });
        
        // 根据类型设置背景色
        if (type === 'success') {
            notification.style.backgroundColor = '#52c41a';
        } else if (type === 'error') {
            notification.style.backgroundColor = '#f5222d';
        } else {
            notification.style.backgroundColor = '#1890ff';
        }
        
        // 添加到页面
        document.body.appendChild(notification);
        
        // 显示通知
        setTimeout(() => {
            notification.style.opacity = '1';
        }, 10);
        
        // 3秒后隐藏通知
        setTimeout(() => {
            notification.style.opacity = '0';
            setTimeout(() => {
                notification.remove();
            }, 300);
        }, 3000);
    }

    // 提取音频URL
    function extractAudioUrl() {
        console.log('正在提取音频URL...');
        
        // 尝试从音频元素获取
        const audioElement = document.querySelector('audio');
        if (audioElement && audioElement.src) {
            console.log('从音频元素找到URL:', audioElement.src);
            return audioElement.src;
        }
        
        // 尝试从视频元素获取
        const videoElement = document.querySelector('video');
        if (videoElement && videoElement.src) {
            console.log('从视频元素找到URL:', videoElement.src);
            return videoElement.src;
        }
        
        // 尝试从meta标签获取
        const metaAudio = document.querySelector('meta[property="og:audio"]');
        if (metaAudio) {
            const audioUrl = metaAudio.getAttribute('content');
            console.log('从meta标签找到URL:', audioUrl);
            return audioUrl;
        }
        
        // 尝试从JavaScript变量中获取
        try {
            const allScripts = document.querySelectorAll('script');
            for (let script of allScripts) {
                const scriptContent = script.textContent;
                if (scriptContent.includes('play_url') || scriptContent.includes('audio_url')) {
                    // 使用正则表达式查找URL
                    const urlMatch = scriptContent.match(/(?:play_url|audio_url)\s*:\s*['"]([^'"]+)['"]/);
                    if (urlMatch && urlMatch[1]) {
                        console.log('从JavaScript变量找到URL:', urlMatch[1]);
                        return urlMatch[1];
                    }
                }
            }
        } catch (e) {
            console.error('提取JavaScript变量时出错:', e);
        }
        
        console.log('未找到音频URL');
        return null;
    }

    // 获取音频信息
    function getAudioInfo() {
        let artist = '未知艺术家';
        let musicTitle = '未知歌曲';
        
        // 尝试获取艺术家信息
        const artistElements = document.querySelectorAll(
            '.author, .creator, .user-name, .profile-name, [class*="author"], [class*="creator"]'
        );
        
        if (artistElements.length > 0) {
            artist = artistElements[0].textContent.trim();
            // 清理艺术家名称
            artist = artist.replace(/^@/, '').replace(/关注/, '').trim();
        }
        
        // 尝试获取歌曲标题
        const titleElements = document.querySelectorAll(
            'h1, .title, .video-title, [class*="title"], [class*="VideoTitle"]'
        );
        
        if (titleElements.length > 0) {
            musicTitle = titleElements[0].textContent.trim();
        }
        
        console.log('获取到的音乐信息:', { artist, musicTitle });
        return { artist, musicTitle };
    }

    // 创建下载按钮
    function createDownloadButton(audioUrl, filename) {
        // 创建按钮容器
        const buttonContainer = document.createElement('div');
        buttonContainer.className = 'tampermonkey-download-container';
        
        // 创建按钮
        const button = document.createElement('button');
        button.textContent = audioUrl ? '💾 下载音乐' : '🔍 寻找音频源...';
        
        // 设置按钮样式
        Object.assign(button.style, {
            backgroundColor: '#1890ff',
            color: 'white',
            border: 'none',
            padding: '12px 24px',
            fontSize: '16px',
            fontWeight: 'bold',
            borderRadius: '8px',
            cursor: 'pointer',
            margin: '20px 0',
            transition: 'all 0.3s ease'
        });
        
        // 添加悬停效果
        button.addEventListener('mouseover', () => {
            button.style.backgroundColor = '#40a9ff';
            button.style.transform = 'translateY(-2px)';
            button.style.boxShadow = '0 4px 12px rgba(24, 144, 255, 0.3)';
        });
        
        button.addEventListener('mouseout', () => {
            button.style.backgroundColor = '#1890ff';
            button.style.transform = 'translateY(0)';
            button.style.boxShadow = 'none';
        });
        
        // 设置点击事件
        if (audioUrl) {
            button.onclick = function() {
                const a = document.createElement('a');
                a.href = audioUrl;
                a.download = filename;
                document.body.appendChild(a);
                a.click();
                document.body.removeChild(a);
                showNotification('下载已开始!', 'success');
            };
        }
        
        // 添加到容器
        buttonContainer.appendChild(button);
        
        // 设置容器样式
        Object.assign(buttonContainer.style, {
            position: 'relative',
            textAlign: 'center',
            marginTop: '20px'
        });
        
        // 找到合适的位置插入按钮
        const insertionPoints = [
            document.querySelector('.video-container'),
            document.querySelector('.player-container'),
            document.querySelector('.content'),
            document.querySelector('main'),
            document.body
        ];
        
        for (const point of insertionPoints) {
            if (point) {
                point.appendChild(buttonContainer);
                console.log('下载按钮已添加到页面');
                return button;
            }
        }
        
        // 如果找不到合适的位置，就添加到body
        document.body.appendChild(buttonContainer);
        return button;
    }

    // 自动点击播放按钮以触发音频加载
    function triggerPlay() {
        console.log('尝试点击播放按钮...');
        const playButtons = document.querySelectorAll(
            'button:contains("播放"), button:contains("play"), ' +
            '[class*="play"], [class*="Play"], [id*="play"], [id*="Play"]'
        );
        
        if (playButtons.length > 0) {
            console.log('找到播放按钮，尝试点击...');
            playButtons[0].click();
            return true;
        }
        
        return false;
    }

    // 主函数
    function main() {
        // 尝试点击播放按钮
        triggerPlay();
        
        // 初始提取
        let audioUrl = extractAudioUrl();
        
        // 获取音乐信息
        const { artist, musicTitle } = getAudioInfo();
        const filename = `${artist} - ${musicTitle}.mp3`;
        
        // 创建下载按钮
        const button = createDownloadButton(audioUrl, filename);
        
        // 如果没找到URL，设置轮询
        if (!audioUrl) {
            showNotification('正在寻找音频源...请稍后', 'info');
            
            let attempts = 0;
            const maxAttempts = 10;
            const interval = setInterval(() => {
                attempts++;
                console.log(`尝试重新提取音频URL (${attempts}/${maxAttempts})...`);
                
                audioUrl = extractAudioUrl();
                if (audioUrl) {
                    console.log('找到音频URL!');
                    button.onclick = function() {
                        const a = document.createElement('a');
                        a.href = audioUrl;
                        a.download = filename;
                        document.body.appendChild(a);
                        a.click();
                        document.body.removeChild(a);
                        showNotification('下载已开始!', 'success');
                    };
                    button.textContent = '💾 下载音乐';
                    showNotification('已找到音频源，点击按钮下载', 'success');
                    clearInterval(interval);
                } else if (attempts >= maxAttempts) {
                    console.log('达到最大尝试次数，停止搜索');
                    button.textContent = '❌ 无法找到音频源';
                    button.style.backgroundColor = '#f5222d';
                    showNotification('无法自动找到音频源，请尝试手动操作', 'error');
                    clearInterval(interval);
                }
            }, 2000); // 每2秒尝试一次
        }
    }

    // 等待页面加载完成
    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', main);
    } else {
        main();
    }
})();'''
    
    script_path = os.path.join(os.getcwd(), '豆包音乐提取器_tampermonkey.js')
    try:
        with open(script_path, 'w', encoding='utf-8') as f:
            f.write(script_content)
        global_logger.info(f"Tampermonkey脚本已生成: {script_path}")
        return script_path
    except Exception as e:
        global_logger.error(f"生成Tampermonkey脚本失败: {str(e)}")
        return None

def setup_chrome_driver(headless=False):
    """设置Chrome浏览器驱动"""
    try:
        options = Options()
        
        # 禁用自动化控制标志，避免被网站检测
        options.add_experimental_option("excludeSwitches", ["enable-automation"])
        options.add_experimental_option("useAutomationExtension", False)
        options.add_argument("--disable-blink-features=AutomationControlled")
        
        # 设置用户代理，模拟真实浏览器
        user_agent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36"
        options.add_argument(f"user-agent={user_agent}")
        
        # 设置无头模式
        if headless:
            options.add_argument("--headless")
            options.add_argument("--disable-gpu")
            options.add_argument("--window-size=1920,1080")
        
        # 禁用图片加载以提高速度
        prefs = {"profile.managed_default_content_settings.images": 2}
        options.add_experimental_option("prefs", prefs)
        
        # 设置其他Chrome选项
        options.add_argument("--no-sandbox")
        options.add_argument("--disable-dev-shm-usage")
        
        return options
    except Exception as e:
        global_logger.error(f"创建Chrome选项时出错: {str(e)}")
        return None

def format_duration(seconds):
    """格式化时长为 mm:ss 格式"""
    try:
        seconds = float(seconds)
        minutes = int(seconds // 60)
        seconds = int(seconds % 60)
        return f"{minutes:02d}:{seconds:02d}"
    except:
        return "00:00"

# 定义下载线程类
class DownloadThread:
    """下载线程类 - 在导入PyQt5之前定义基础版本"""
    def __init__(self, *args, **kwargs):
        pass
    def start(self):
        pass
    def stop(self):
        pass

# 定义主函数，在导入PyQt5之前
def main():
    """程序主入口函数"""
    # 尝试导入PyQt5相关模块
    try:
        print("正在尝试导入PyQt5...")
        from PyQt5.QtWidgets import (
            QApplication, QMainWindow, QWidget, QVBoxLayout, 
            QHBoxLayout, QLabel, QLineEdit, QPushButton, 
            QProgressBar, QTextEdit, QCheckBox, QMessageBox, 
            QFileDialog, QGroupBox, QFormLayout
        )
        from PyQt5.QtCore import Qt, QThread, pyqtSignal
        from PyQt5.QtGui import QIcon, QFont, QTextOption, QIntValidator
        print("✅ PyQt5模块导入成功")
        
        # 重新定义DownloadThread类，使用QThread
        class DownloadThread(QThread):
            """下载线程类"""
            update_progress = pyqtSignal(int)
            update_status = pyqtSignal(str)
            download_complete = pyqtSignal(bool, str)
            music_info_found = pyqtSignal(dict)
            update_download_speed = pyqtSignal(str)
            update_estimated_time = pyqtSignal(str)
            
            def __init__(self, share_url, save_path=None, use_selenium=False, max_retries=3):
                super().__init__()
                self.share_url = share_url
                self.save_path = save_path
                self.use_selenium = use_selenium
                self.max_retries = max_retries
                self.is_running = True
                self.start_time = None
                self.last_downloaded_size = 0
                self.last_speed_time = 0
                
            def _create_unique_filename(self, music_info, output_dir):
                """创建唯一的文件名"""
                # 构建基础文件名
                base_filename = f"{music_info['artist']} - {music_info['title']}.mp3"
                sanitized_filename = sanitize_filename(base_filename)
                
                # 构建完整路径
                file_path = os.path.join(output_dir, sanitized_filename)
                
                # 检查文件是否已存在，如果存在则添加数字后缀
                counter = 1
                name, ext = os.path.splitext(file_path)
                while os.path.exists(file_path):
                    file_path = f"{name} ({counter}){ext}"
                    counter += 1
                
                return file_path
            
            def _update_progress_smoothly(self, current_progress):
                """平滑更新进度"""
                self.update_progress.emit(current_progress)
            
            def _calculate_download_speed(self, downloaded_bytes):
                """计算下载速度"""
                current_time = time.time()
                if not self.start_time:
                    self.start_time = current_time
                    self.last_downloaded_size = downloaded_bytes
                    self.last_speed_time = current_time
                    return "--"
                
                # 每0.5秒更新一次速度
                if current_time - self.last_speed_time >= 0.5:
                    bytes_per_second = (downloaded_bytes - self.last_downloaded_size) / (current_time - self.last_speed_time)
                    self.last_downloaded_size = downloaded_bytes
                    self.last_speed_time = current_time
                    
                    # 转换为合适的单位
                    if bytes_per_second < 1024:
                        return f"{bytes_per_second:.1f} B/s"
                    elif bytes_per_second < 1024 * 1024:
                        return f"{bytes_per_second / 1024:.1f} KB/s"
                    else:
                        return f"{bytes_per_second / (1024 * 1024):.1f} MB/s"
                
                return "--"
            
            def _calculate_estimated_time(self, downloaded_bytes):
                """计算预计剩余时间"""
                # 这个功能需要知道总文件大小，暂时返回占位符
                return "--"
            
            def run(self):
                """线程运行函数"""
                try:
                    global_logger.info(f"开始下载任务: {self.share_url}")
                    self.update_status.emit("🔍 正在解析分享链接...")
                    self.update_progress.emit(0)  # 初始进度
                    
                    # 解析链接
                    vid = parse_share_url(self.share_url)
                    if not vid:
                        self.update_status.emit("❌ 无法解析分享链接")
                        self.download_complete.emit(False, "")
                        return
                    
                    # 获取音乐信息
                    self.update_status.emit("📝 正在获取音乐信息...")
                    self.update_progress.emit(10)
                    
                    music_info = None
                    retry = 0
                    
                    while retry < self.max_retries and self.is_running:
                        if self.use_selenium:
                            # 使用Selenium模式获取音乐信息
                            music_info = get_music_info_with_selenium(vid)
                        else:
                            # 使用普通模式获取音乐信息
                            music_info = get_music_info(vid)
                        
                        if music_info:
                            break
                        
                        retry += 1
                        if retry < self.max_retries:
                            self.update_status.emit(f"⚠️ 获取音乐信息失败，第{retry + 1}次尝试中...")
                            time.sleep(2)  # 等待2秒后重试
                    
                    if not music_info:
                        self.update_status.emit("❌ 无法获取音乐信息，已达到最大重试次数")
                        self.download_complete.emit(False, "")
                        return
                    
                    # 发送音乐信息信号
                    self.music_info_found.emit(music_info)
                    self.update_status.emit("🎵 音乐信息获取成功")
                    self.update_progress.emit(30)
                    
                    # 创建保存目录
                    if not self.save_path:
                        self.save_path = os.path.join(os.getcwd(), 'downloads')
                    
                    if not os.path.isdir(self.save_path):
                        os.makedirs(self.save_path, exist_ok=True)
                    
                    # 创建保存路径
                    save_file_path = self._create_unique_filename(music_info, self.save_path)
                    
                    # 开始下载
                    self.update_status.emit("⬇️ 正在下载音乐文件...")
                    
                    success = False
                    if self.use_selenium:
                        # 使用Selenium模式下载
                        success = download_music_with_selenium(self.share_url, save_file_path)
                    else:
                        # 使用普通模式下载
                        success = download_music_file(music_info['audio_url'], save_file_path, self)
                    
                    if success:
                        self.update_status.emit("✅ 音乐下载成功")
                        self.update_progress.emit(100)
                        self.download_complete.emit(True, save_file_path)
                    else:
                        self.update_status.emit("❌ 音乐下载失败")
                        self.download_complete.emit(False, "")
                
                except Exception as e:
                    global_logger.error(f"下载过程中发生错误: {str(e)}")
                    import traceback
                    global_logger.debug(traceback.format_exc())
                    self.update_status.emit(f"❌ 下载异常: {str(e)}")
                    self.download_complete.emit(False, "")
            
            def stop(self):
                """停止下载任务"""
                self.is_running = False
                self.wait()
        
        # 定义GUI类
        class MusicDownloaderGUI(QMainWindow):
            """豆包音乐下载器图形界面类"""
            
            def __init__(self):
                super().__init__()
                self.setWindowTitle("豆包音乐下载器 v1.4")
                self.setGeometry(100, 100, 800, 600)
                self.download_thread = None
                self.init_ui()
            
            def init_ui(self):
                """初始化用户界面"""
                # 设置主布局
                central_widget = QWidget()
                self.setCentralWidget(central_widget)
                main_layout = QVBoxLayout(central_widget)
                
                # 创建输入区域
                input_group = QGroupBox("输入信息")
                input_layout = QFormLayout()
                
                # 分享链接输入框
                self.url_input = QLineEdit()
                self.url_input.setPlaceholderText("请输入豆包音乐分享链接")
                self.url_input.setMinimumHeight(30)
                input_layout.addRow("分享链接:", self.url_input)
                
                # 保存路径选择
                path_layout = QHBoxLayout()
                self.path_input = QLineEdit()
                self.path_input.setPlaceholderText("选择保存目录")
                self.path_input.setMinimumHeight(30)
                self.path_input.setText(os.path.join(os.getcwd(), 'downloads'))
                
                browse_button = QPushButton("浏览...")
                browse_button.clicked.connect(self.on_browse_clicked)
                browse_button.setMinimumHeight(30)
                
                path_layout.addWidget(self.path_input)
                path_layout.addWidget(browse_button)
                input_layout.addRow("保存目录:", path_layout)
                
                # 选项设置
                options_layout = QHBoxLayout()
                
                # 使用Selenium模式复选框
                self.selenium_checkbox = QCheckBox("使用Selenium模式")
                if not selenium_available:
                    self.selenium_checkbox.setChecked(False)
                    self.selenium_checkbox.setEnabled(False)
                    self.selenium_checkbox.setToolTip("Selenium模块未安装")
                
                options_layout.addWidget(self.selenium_checkbox)
                options_layout.addStretch()
                input_layout.addRow(options_layout)
                
                # 添加输入区域到主布局
                input_group.setLayout(input_layout)
                main_layout.addWidget(input_group)
                
                # 创建控制按钮区域
                control_group = QGroupBox("操作")
                control_layout = QHBoxLayout()
                
                # 解析按钮
                self.parse_button = QPushButton("📋 解析链接")
                self.parse_button.setMinimumHeight(35)
                control_layout.addWidget(self.parse_button)
                
                # 下载按钮
                self.download_button = QPushButton("⬇️ 下载音乐")
                self.download_button.setMinimumHeight(35)
                self.download_button.setEnabled(False)  # 初始禁用
                control_layout.addWidget(self.download_button)
                
                # 清理按钮
                self.clear_button = QPushButton("🧹 清理")
                self.clear_button.setMinimumHeight(35)
                control_layout.addWidget(self.clear_button)
                
                # 关于按钮
                self.about_button = QPushButton("❓ 关于")
                self.about_button.setMinimumHeight(35)
                control_layout.addWidget(self.about_button)
                
                # 添加控制区域到主布局
                control_group.setLayout(control_layout)
                main_layout.addWidget(control_group)
                
                # 创建音乐信息区域
                info_group = QGroupBox("音乐信息")
                info_layout = QFormLayout()
                
                # 音乐信息输入框
                self.title_input = QLineEdit()
                self.title_input.setReadOnly(True)
                self.title_input.setMinimumHeight(30)
                info_layout.addRow("歌曲标题:", self.title_input)
                
                self.artist_input = QLineEdit()
                self.artist_input.setReadOnly(True)
                self.artist_input.setMinimumHeight(30)
                info_layout.addRow("艺术家:", self.artist_input)
                
                self.duration_input = QLineEdit()
                self.duration_input.setReadOnly(True)
                self.duration_input.setMinimumHeight(30)
                info_layout.addRow("时长:", self.duration_input)
                
                self.size_input = QLineEdit()
                self.size_input.setReadOnly(True)
                self.size_input.setMinimumHeight(30)
                info_layout.addRow("文件大小:", self.size_input)
                
                # 添加音乐信息区域到主布局
                info_group.setLayout(info_layout)
                main_layout.addWidget(info_group)
                
                # 创建进度区域
                progress_group = QGroupBox("下载进度")
                progress_layout = QVBoxLayout()
                
                # 进度条
                self.progress_bar = QProgressBar()
                self.progress_bar.setValue(0)
                self.progress_bar.setTextVisible(True)
                self.progress_bar.setMinimumHeight(25)
                progress_layout.addWidget(self.progress_bar)
                
                # 状态信息
                status_layout = QHBoxLayout()
                
                # 状态标签
                self.status_label = QLabel("就绪")
                status_layout.addWidget(self.status_label)
                
                # 下载速度
                self.speed_label = QLabel("速度: --")
                status_layout.addWidget(self.speed_label)
                
                # 预计时间
                self.time_label = QLabel("剩余: --")
                status_layout.addWidget(self.time_label)
                
                status_layout.addStretch()
                progress_layout.addLayout(status_layout)
                
                # 添加进度区域到主布局
                progress_group.setLayout(progress_layout)
                main_layout.addWidget(progress_group)
                
                # 创建日志区域
                log_group = QGroupBox("日志信息")
                log_layout = QVBoxLayout()
                
                # 日志文本框
                self.log_text = QTextEdit()
                self.log_text.setReadOnly(True)
                self.log_text.setLineWrapMode(QTextEdit.WidgetWidth)
                self.log_text.setWordWrapMode(QTextOption.WrapAtWordBoundaryOrAnywhere)
                log_layout.addWidget(self.log_text)
                
                # 添加日志区域到主布局
                log_group.setLayout(log_layout)
                main_layout.addWidget(log_group)
                
                # 添加状态栏
                self.statusBar().showMessage("就绪")
                
                # 添加事件处理
                self.parse_button.clicked.connect(self.on_parse_clicked)
                self.download_button.clicked.connect(self.on_download_clicked)
                self.clear_button.clicked.connect(self.on_clear_clicked)
                self.about_button.clicked.connect(self.on_about_clicked)
                self.url_input.returnPressed.connect(self.on_parse_clicked)
            
            def on_browse_clicked(self):
                """浏览文件夹"""
                directory = QFileDialog.getExistingDirectory(self, "选择保存目录")
                if directory:
                    self.path_input.setText(directory)
            
            def on_parse_clicked(self):
                """解析链接"""
                share_url = self.url_input.text().strip()
                if not share_url:
                    QMessageBox.warning(self, "警告", "请输入分享链接")
                    return
                
                self.status_label.setText("🔍 正在解析链接...")
                self.statusBar().showMessage("正在解析链接")
                
                # 尝试解析链接
                vid = parse_share_url(share_url)
                if not vid:
                    QMessageBox.critical(self, "错误", "无法解析分享链接")
                    self.status_label.setText("❌ 解析失败")
                    self.statusBar().showMessage("解析失败")
                    return
                
                # 获取音乐信息
                self.status_label.setText("📝 正在获取音乐信息...")
                
                use_selenium = self.selenium_checkbox.isChecked()
                music_info = None
                
                if use_selenium and selenium_available:
                    music_info = get_music_info_with_selenium(vid)
                else:
                    music_info = get_music_info(vid)
                
                if not music_info:
                    # 如果普通模式失败且Selenium可用，尝试使用Selenium
                    if selenium_available and not use_selenium:
                        self.status_label.setText("📝 尝试使用Selenium获取...")
                        music_info = get_music_info_with_selenium(vid)
                    
                    if not music_info:
                        QMessageBox.critical(self, "错误", "无法获取音乐信息")
                        self.status_label.setText("❌ 获取信息失败")
                        self.statusBar().showMessage("获取信息失败")
                        return
                
                # 显示音乐信息
                self.title_input.setText(music_info.get('title', '未知歌曲'))
                self.artist_input.setText(music_info.get('artist', '未知艺术家'))
                self.duration_input.setText(format_duration(music_info.get('duration', 0)))
                self.size_input.setText("未知")  # 暂时无法获取文件大小
                
                # 启用下载按钮
                self.download_button.setEnabled(True)
                self.status_label.setText("✅ 解析成功")
                self.statusBar().showMessage("解析完成")
            
            def on_download_clicked(self):
                """下载音乐"""
                share_url = self.url_input.text().strip()
                save_path = self.path_input.text().strip()
                use_selenium = self.selenium_checkbox.isChecked()
                
                # 验证输入
                if not share_url:
                    QMessageBox.warning(self, "警告", "请输入分享链接")
                    return
                
                if not save_path:
                    QMessageBox.warning(self, "警告", "请选择保存目录")
                    return
                
                # 禁用按钮，防止重复点击
                self.parse_button.setEnabled(False)
                self.download_button.setEnabled(False)
                self.clear_button.setEnabled(False)
                
                # 创建并启动下载线程
                self.download_thread = DownloadThread(
                    share_url=share_url,
                    save_path=save_path,
                    use_selenium=use_selenium
                )
                
                # 连接信号
                self.download_thread.update_progress.connect(self.on_progress_update)
                self.download_thread.update_status.connect(self.on_status_update)
                self.download_thread.download_complete.connect(self.on_download_complete)
                self.download_thread.music_info_found.connect(self.on_music_info_found)
                self.download_thread.update_download_speed.connect(self.on_download_speed_update)
                self.download_thread.update_estimated_time.connect(self.on_estimated_time_update)
                
                # 启动线程
                self.download_thread.start()
            
            def on_progress_update(self, progress):
                """更新进度条"""
                self.progress_bar.setValue(progress)
            
            def on_status_update(self, status):
                """更新状态信息"""
                self.status_label.setText(status)
                self.statusBar().showMessage(status)
                # 添加到日志
                self.log_text.append(f"[{datetime.now().strftime('%H:%M:%S')}] {status}")
            
            def on_music_info_found(self, music_info):
                """更新音乐信息"""
                self.title_input.setText(music_info.get('title', '未知歌曲'))
                self.artist_input.setText(music_info.get('artist', '未知艺术家'))
                self.duration_input.setText(format_duration(music_info.get('duration', 0)))
            
            def on_download_speed_update(self, speed):
                """更新下载速度"""
                self.speed_label.setText(f"速度: {speed}")
            
            def on_estimated_time_update(self, estimated_time):
                """更新预计时间"""
                self.time_label.setText(f"剩余: {estimated_time}")
            
            def on_download_complete(self, success, file_path):
                """下载完成处理"""
                # 恢复按钮状态
                self.parse_button.setEnabled(True)
                self.download_button.setEnabled(True)
                self.clear_button.setEnabled(True)
                
                if success:
                    QMessageBox.information(self, "成功", f"音乐下载成功！\n\n保存路径: {file_path}")
                else:
                    QMessageBox.critical(self, "失败", "音乐下载失败，请检查日志信息")
            
            def on_clear_clicked(self):
                """清理输入和信息"""
                # 清除输入
                self.url_input.clear()
                
                # 清除音乐信息
                self.title_input.clear()
                self.artist_input.clear()
                self.duration_input.clear()
                self.size_input.clear()
                
                # 重置进度
                self.progress_bar.setValue(0)
                self.status_label.setText("就绪")
                self.speed_label.setText("速度: --")
                self.time_label.setText("剩余: --")
                
                # 重置状态栏
                self.statusBar().showMessage("就绪")
                
                # 禁用下载按钮
                self.download_button.setEnabled(False)
            
            def on_about_clicked(self):
                """显示关于对话框"""
                QMessageBox.about(self, "关于", "豆包音乐下载器 v1.4\n\n用于下载豆包音乐分享链接中的音乐\n\n支持普通模式和Selenium浏览器自动化模式\n\n作者: AI Assistant")
        
        # 创建应用程序并显示窗口
        print("正在创建QApplication...")
        app = QApplication(sys.argv)
        app.setStyle("Fusion")
        
        print("正在创建主窗口...")
        window = MusicDownloaderGUI()
        print("显示窗口...")
        window.show()
        
        print("启动应用程序主循环...")
        sys.exit(app.exec_())
        
    except ImportError as e:
        print(f"❌ PyQt5导入失败: {str(e)}")
        print("请安装PyQt5后重试: pip install PyQt5")
        sys.exit(1)
    except Exception as e:
        print(f"❌ 应用程序启动失败: {str(e)}")
        import traceback
        traceback.print_exc()
        sys.exit(1)

if __name__ == "__main__":
    """程序主入口"""
    main()
